// Copyright 2022 jeff.li. and/or its affiliates.

#pragma once

#include <tuple>
#include <type_traits>

#include "generic_funcs.h"

namespace tbir::runtime {

    namespace {
        template<class...>
        using to_void_t_impl = void;

        template<typename T>
        auto has_size_method_impl(int) -> decltype(std::declval<T>().size(), std::true_type{});

        template<typename T>
        auto has_size_method_impl(...) -> std::false_type;

        template<typename, typename = void>
        struct has_get_item_int_method_impl : std::false_type {
        };

        template<typename T>
        struct has_get_item_int_method_impl<T, to_void_t_impl<decltype(&T::get_item)>> : std::true_type {
        };

        template<typename T>
        using has_size_method = decltype(has_size_method_impl<T>(0));
    }  // namespace

    struct KernelBuiltinsLenDetails {
        template<typename... ARGS>
        TURBO_FORCE_INLINE static int64_t run(const std::tuple<ARGS...> &container) {
            return std::tuple_size<std::tuple<ARGS...>>{};
        }

        template<typename T1, typename T2>
        TURBO_FORCE_INLINE static int64_t run(const std::pair<T1, T2> &container) {
            return 2;
        }

        template<typename T, typename = typename std::enable_if<has_size_method<T>::value>::type>
        TURBO_FORCE_INLINE static int64_t run(const T &container) {
            return container.size();
        }

        TURBO_FORCE_INLINE static int64_t run(const Any &container) {
            return kernel_object___len__(container);
        }
    };

    template<int64_t index, typename R>
    struct KernelBuiltinsGetItemDetails {
        using return_type = typename std::remove_cv<typename std::remove_reference<R>::type>::type;

        template<typename... ARGS>
        TURBO_FORCE_INLINE static return_type run(const std::tuple<ARGS...> &container) {
            return std::get<index>(container);
        }

        template<typename T,
                typename = typename std::enable_if<has_get_item_int_method_impl<T>::value>::type>
        TURBO_FORCE_INLINE static return_type run(const T &container) {
            GenericValueConverter<return_type> converter;
            return converter(container.get_item(index));
        }

        TURBO_FORCE_INLINE static return_type run(const Any &container) {
            static_assert(std::is_same<return_type, RTValue>::value,
                          "Any.__getitem__ return_type must be Any");
            return kernel_object___getitem__(container, RTView(index));
        }
    };

    template<typename R>
    struct KernelBuiltinsGetItemDetails<0, R> {
        using return_type = typename std::remove_cv<typename std::remove_reference<R>::type>::type;

        template<typename... ARGS>
        TURBO_FORCE_INLINE static return_type run(const std::tuple<ARGS...> &container) {
            return std::get<0>(container);
        }

        template<typename T1, typename T2>
        TURBO_FORCE_INLINE static return_type run(const std::pair<T1, T2> &container) {
            return container.first;
        }

        template<typename T,
                typename = typename std::enable_if<has_get_item_int_method_impl<T>::value>::type>
        TURBO_FORCE_INLINE static return_type run(const T &container) {
            GenericValueConverter<return_type> converter;
            return converter(container.get_item(0));
        }

        TURBO_FORCE_INLINE static return_type run(const Any &container) {
            static_assert(std::is_same<return_type, RTValue>::value,
                          "internal error: Any.__getitem__ return_type must be Any");
            return kernel_object___getitem__(container, RTView(0));
        }
    };

    template<typename R>
    struct KernelBuiltinsGetItemDetails<1, R> {
        using return_type = typename std::remove_cv<typename std::remove_reference<R>::type>::type;

        template<typename... ARGS>
        TURBO_FORCE_INLINE static return_type run(const std::tuple<ARGS...> &container) {
            return std::get<1>(container);
        }

        template<typename T1, typename T2>
        TURBO_FORCE_INLINE static return_type run(const std::pair<T1, T2> &container) {
            return container.second;
        }

        template<typename T,
                typename = typename std::enable_if<has_get_item_int_method_impl<T>::value>::type>
        TURBO_FORCE_INLINE static return_type run(const T &container) {
            GenericValueConverter<return_type> converter;
            return converter(container.get_item(1));
        }

        TURBO_FORCE_INLINE static return_type run(const Any &container) {
            static_assert(std::is_same<return_type, RTValue>::value,
                          "internal error: Any.__getitem__ return_type must be Any");
            return kernel_object___getitem__(container, RTView(1));
        }
    };

    template<int64_t index, typename R, typename T>
    TURBO_FORCE_INLINE R kernel_builtins_unpack(const T &container) {
        return KernelBuiltinsGetItemDetails<index, R>::run(container);
    }

    template<typename T>
    TURBO_FORCE_INLINE int64_t kernel_builtins_len(const T &container) {
        return KernelBuiltinsLenDetails::run(container);
    }

}  // namespace tbir::runtime
